/*
 * Copyright 2009 BioTeam Inc
 * 
 * Licensed under License GNU LESSER GENERAL PUBLIC LICENSE version 3.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.gnu.org/copyleft/lesser.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package net.bioteam.appweb.gwt.apps.server.user;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.Query;
import javax.servlet.http.HttpServletResponse;

import net.bioteam.appweb.Config;
import net.bioteam.cloud.cluster.ActiveCluster;
import net.bioteam.cloud.cluster.Cluster;
import net.bioteam.cloud.cluster.ClusterData;
import net.bioteam.cloud.cluster.ClusterStatus;
import net.bioteam.cloud.cluster.ClusterUtil;
import net.bioteam.cloud.cluster.Host;
import net.bioteam.appweb.gwt.apps.clientandserver.ClusterInfo;
import net.bioteam.appweb.gwt.apps.clientandserver.ServerException;
import net.bioteam.appweb.gwt.apps.clientandserver.user.ClusterRet;
import net.bioteam.appweb.gwt.apps.clientandserver.user.JobRet;
import net.bioteam.appweb.gwt.apps.clientandserver.user.UserAction;
import net.bioteam.appweb.gwt.apps.clientandserver.user.UserRet;
import net.bioteam.appweb.gwt.apps.server.MyAuthException;
import net.bioteam.appweb.gwt.apps.server.MyRemoteServiceServlet;
import net.bioteam.appweb.model.ProgramTemplate;
import net.bioteam.appweb.model.ProgramTemplateUtil;
import net.bioteam.appweb.submit.RunStatus;
import net.bioteam.appweb.submit.SubmitterUtil;
import net.bioteam.appweb.submit.UnfinishedJob;
import net.bioteam.common.user.User;
import net.bioteam.common.user.UserInfo;
import net.bioteam.common.utils.MyEntityManager;
import net.bioteam.common.utils.Utils;

import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axis2.AxisFault;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class UserActionImpl extends MyRemoteServiceServlet implements UserAction
{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public final static Log logger = LogFactory.getLog(UserActionImpl.class);

	public String deleteJobs(String[] ids) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		try
		{
			EntityManager em = MyEntityManager.getEntityManager();
			EntityTransaction tx = em.getTransaction();
			em.setFlushMode(FlushModeType.COMMIT);
			try
			{
				tx.begin();
				for (String id : ids)
				{
					RunStatus job = em.find(RunStatus.class, Long.parseLong(id));
					if (job == null)
						continue;
					if (!job.getUsername().equals(user.getIdAsString()))
					{
						throw new ServerException(" You do not have permission to delete one of the jobs");
					}
					if (job.isRunning())
					{
						UnfinishedJob uj = em.find(UnfinishedJob.class, Long.parseLong(id));
						if (uj != null)
						{
							throw new ServerException(" You can not delete running jobs");
						}
					}
					// this is peformed in database as trigger
					// UnfinishedJob uj=em.find(UnfinishedJob.class,
					// job.getId());
					// if (uj!=null)
					// {
					// em.remove(uj);
					// }
					em.remove(job);
					File workdir = new File(job.getWorkdir());
					if (workdir.exists())
					{
						logger.info("delete working directory " + workdir);
						FileUtils.deleteDirectory(workdir);
					}
				}
				tx.commit();
			} catch (RuntimeException e1)
			{
				try
				{
					tx.rollback();
				} catch (Exception e2)
				{
					// ignore.
				}
				throw e1;
			}
			return "Requested Records are deleted";

		} catch (Exception e)
		{
			logger.warn("template error", e);
			throw new ServerException(e.getMessage());
		}
	}

	public String killJob(String jobid) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		EntityManager em = MyEntityManager.getEntityManager();
		RunStatus rs = em.find(RunStatus.class, Long.parseLong(jobid));
		if (rs == null || !rs.getUsername().equals(String.valueOf(user.getUserid())))
		{
			throw new ServerException("user=" + user.getName() + ", jobid=" + jobid + " does not exist");
		}

		String message = " Job is marked for deletion.";
		try
		{

			SubmitterUtil.getSubmitter(rs.getClusterid()).killJob(rs);
		} catch (Exception e)
		{
			logger.info(e.getMessage());
			message = e.getMessage();
		}
		return message;
	}

	public String deletetemplate(String[] ids) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		try
		{
			EntityManager em = MyEntityManager.getEntityManager();
			EntityTransaction tx = em.getTransaction();
			em.setFlushMode(FlushModeType.COMMIT);
			try
			{
				tx.begin();
				for (String id : ids)
				{
					ProgramTemplate run = em.find(ProgramTemplate.class, Long.parseLong(id));
					if (run == null)
						continue;
					if (!run.getUsername().equals(user.getIdAsString()))
					{
						throw new ServerException(" You do not have permission to delete one of the templates");
					}
					em.remove(run);
					File targetFile = new File(Config.topdir + File.separator + Config.TEMPLATE_DIR + File.separator + user.getName() + File.separator + ProgramTemplateUtil.getFilename(run));
					if (targetFile.exists())
					{
						targetFile.delete();
					}
				}
				tx.commit();
			} catch (RuntimeException e1)
			{
				try
				{
					tx.rollback();
				} catch (Exception e2)
				{
					// ignore.
				}
				throw e1;
			}
			return "Requested Records are deleted";

		} catch (Exception e)
		{
			logger.warn("template error", e);
			throw new ServerException(e.getMessage());
		}
	}

	// TODO honor parameters
	public JobRet getJobs(int start, int limit, String sort, String direction) throws ServerException
	{
		EntityManager em = MyEntityManager.getEntityManager();
		try
		{
			User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
			if (sort != null)
			{
				sort = "rs." + sort;
			} else
			{
				sort = "rs.submittedTime";
			}
			if (direction == null)
			{
				direction = "desc";
			}
			EntityTransaction tx = em.getTransaction();
			tx.begin();
			JobRet ret = new JobRet();
			Query totalQuery = em.createNamedQuery("numofjobs");
			totalQuery.setParameter("username", user.getIdAsString());

			int count = 0;
			try
			{
				Number result = (Number) totalQuery.getSingleResult();
				count = result.intValue();
			} catch (javax.persistence.NoResultException e)
			{
				// nothing count is 0.
			}
			ret.count = count;
			if (count == 0)
			{
				ret.statuses = new RunStatus[0];
			} else
			{
				Query query = em.createQuery("select rs from RunStatus  rs where rs.username = '" + user.getIdAsString() + "' order by " + sort + " " + direction);
				query.setMaxResults(limit);
				query.setFirstResult(start);
				List lists = query.getResultList();
				RunStatus statuses[] = new RunStatus[lists.size()];
				int i = 0;
				for (Iterator iter = lists.iterator(); iter.hasNext(); i++)
				{
					RunStatus status = (RunStatus) iter.next();
					statuses[i] = status;
				}
				ret.statuses = statuses;
			}
			tx.commit();
			return ret;
		} catch (Exception e)
		{
			logger.warn("user jobs error", e);
			throw new ServerException(e.getMessage());
		}

	}

	public ProgramTemplate[] getTemplates() throws ServerException
	{
		EntityManager em = MyEntityManager.getEntityManager();
		EntityTransaction tx = em.getTransaction();
		User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
		try
		{

			tx.begin();
			Query query = em.createNamedQuery("savedruns");
			query.setParameter("username", user.getIdAsString());
			List lists = query.getResultList();
			File savedDir = new File(Config.topdir + File.separator + Config.TEMPLATE_DIR + File.separator + user.getName());
			if (!savedDir.exists())
			{
				savedDir.mkdirs();
			}

			List<ProgramTemplate> runs = new ArrayList<ProgramTemplate>();
			for (Object obj : lists)
			{
				ProgramTemplate run = (ProgramTemplate) obj;
				File file = new File(savedDir.getAbsolutePath() + File.separator + ProgramTemplateUtil.getFilename(run));
				if (file.exists())
				{
					runs.add(run);
				} else
				{
					em.remove(run);
				}
			}

			tx.commit();

			return runs.toArray(new ProgramTemplate[0]);

		} catch (Exception e)
		{
			try
			{
				tx.rollback();
			} catch (RuntimeException e1)
			{
				// ignore
			}
			logger.warn("template error", e);
			throw new ServerException(e.getMessage());
		}

	}

	public ProgramTemplate[] updateTemplates(ProgramTemplate[] runs) throws ServerException
	{
		try
		{
			EntityManager em = MyEntityManager.getEntityManager();
			EntityTransaction tx = em.getTransaction();
			em.setFlushMode(FlushModeType.COMMIT);

			try
			{
				tx.begin();
				for (int i = 0; i < runs.length; i++)
				{
					ProgramTemplate run = runs[i];
					ProgramTemplate pt = em.find(ProgramTemplate.class, run.getId());

					// only two fields need updating.
					pt.setName(run.getName());
					pt.setDescription(run.getDescription());
					// a refresh will discard all dirty data.
					// em.refresh(run);
					runs[i] = pt;

				}
				tx.commit();
			} catch (RuntimeException e1)
			{
				try
				{
					tx.rollback();
				} catch (Exception e2)
				{
					// ignore.
				}
				throw e1;
			}
			return runs;

		} catch (Exception e)
		{
			logger.warn("template error", e);
			throw new ServerException(e.getMessage());
		}
	}

	public User getAccount() throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
		User retUser = new User();
		retUser.setEmail(user.getEmail());
		retUser.setRole(user.getRole());
		retUser.setUserInfo(user.getUserInfo());
		return retUser;
	}

	public User changePassword(String oldPw, String newPw) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
		if (!oldPw.equals(user.getPassword()))
		{
			throw new ServerException("old password does not match");
		}
		user.setPassword(newPw);
		updateAccount(user);
		return getAccount();
	}

	public User changeEmail(String email) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
		user.setEmail(email);
		updateAccount(user);
		return getAccount();
	}

	public String deleteEc2Info() throws ServerException
	{
		ActiveCluster[] acs = getActiveCluster(true);
		if (acs.length > 0)
		{
			throw new ServerException("There are some active cluster running. " + "EC2 information can not be deleted right now. " + "It can be deleted after the cluster is shutdown.");
		}
		User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
		user.getUserInfo().setEc2Userid(null);
		user.getUserInfo().setAccessKey(null);
		user.getUserInfo().setSecretKey(null);
		updateAccount(user);
		return "EC2 info is deleted";
	}

	public User attachEC2Info(UserInfo info) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession(true).getAttribute("user");
		user.setUserInfo(info);
		updateAccount(user);
		return getAccount();
	}

	public User updateAccount(User user) throws ServerException
	{

		try
		{
			EntityManager em = MyEntityManager.getEntityManager();
			EntityTransaction tx = em.getTransaction();
			// em.setFlushMode(FlushModeType.COMMIT);

			try
			{
				tx.begin();
				user = em.merge(user);
				// a refresh before commit essentially discard all
				// modification/all dirty data. So do not refresh before commit.
				// em.refresh(user);
				tx.commit();
			} catch (RuntimeException e1)
			{
				try
				{
					tx.rollback();
				} catch (Exception e2)
				{
					// ignore.
				}
				throw e1;
			}
			getThreadLocalRequest().getSession(true).setAttribute("user", user);
			getThreadLocalRequest().getSession(true).setAttribute("username", user.getName());

			return user;
		} catch (Exception e)
		{
			logger.warn("template error", e);
			throw new ServerException(e.getMessage());
		}
	}

	public User getLoginUser() throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		User ret = new User();
		ret.setName(user.getName());
		ret.setRole(user.getRole());
		return ret;
	}

	public ActiveCluster[] getActiveCluster(boolean allClusters) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");

		if (!user.hasECCredential())
		{
			ServerException e = new ServerException("No EC certificate");
			e.setCode(ServerException.NO_EC);
			throw e;
		}

		EntityManager em = MyEntityManager.getEntityManager();
		EntityTransaction tx = em.getTransaction();
		Query query = em.createNamedQuery("getallactivecluster");

		query.setParameter("userid", user.getIdAsString());
		List lists = query.getResultList();

		List<ActiveCluster> acs = new LinkedList<ActiveCluster>();
		for (Object obj : lists)
		{
			ActiveCluster ac = (ActiveCluster) Config.beanManager.clone(obj);
			if (allClusters)
			{
				acs.add(ac);
			} else if (!ac.getHosts().isEmpty())
				acs.add(ac);
		}
		return acs.toArray(new ActiveCluster[0]);
	}

	public ActiveCluster startCluster(ClusterData data) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		ActiveCluster cluster = new ActiveCluster();
		cluster.setUserid(user.getIdAsString());
		cluster.setDurationhour(data.duration);
		cluster.setRequestTime(new Date());
		cluster.setStatus(ClusterStatus.PRESTART);
		cluster.setKillifidle(data.killifidle);
		EntityManager em = MyEntityManager.getEntityManager();
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		em.persist(cluster);
		tx.commit();

		ClusterUtil.kickStart(cluster, data);
		return (ActiveCluster) Config.beanManager.clone(cluster);
	}

	public String killCluster(long id) throws ServerException
	{
		// User user = (User)
		// getThreadLocalRequest().getSession().getAttribute("user");
		//
		// if (!user.hasECCredential())
		// {
		// ServerException e = new ServerException("No EC certificate");
		// e.setCode(ServerException.NO_EC);
		// throw e;
		// }
		//
		// EntityManager em = MyEntityManager.getEntityManager();
		// ActiveCluster ac=em.find(ActiveCluster.class, id);
		// if (ac==null)
		// {
		// throw new
		// ServerException("specified cluster is not found. It may already be shut down");
		// }
		// EntityTransaction tx=em.getTransaction();
		// tx.begin();
		// ac.setStatus(ClusterStatus.HALTED);
		// tx.commit();

		return "cluster is scheduled for shut down";
	}

	public RunStatus refreshJob(long id) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		EntityManager em = MyEntityManager.getEntityManager();
		RunStatus rs = em.find(RunStatus.class, id);
		if (Long.parseLong(rs.getUsername()) != user.getUserid())
		{
			throw new ServerException("You do not have permission to access this job");
		}
		return rs;
	}

	public String getClusterKey(long id) throws ServerException
	{

		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");
		EntityManager em = MyEntityManager.getEntityManager();
		ActiveCluster cluster = em.find(ActiveCluster.class, id);
		if (cluster == null)
		{
			throw new ServerException("specified cluster is not found. It may have been shut down");
		}
		return cluster.getKeyMaterial();

	}

	public ClusterRet getClusterHistory(int start, int limit) throws ServerException
	{
		User user = (User) getThreadLocalRequest().getSession().getAttribute("user");

		try
		{
			EntityManager em = MyEntityManager.getEntityManager();
			EntityTransaction tx = em.getTransaction();

			tx.begin();
			Query totalQuery = em.createNamedQuery("numofcluster");
			totalQuery.setParameter("userid", user.getIdAsString());

			ClusterRet ret = new ClusterRet();
			int count = 0;
			try
			{
				Number result = (Number) totalQuery.getSingleResult();
				count = result.intValue();
			} catch (javax.persistence.NoResultException e)
			{
				// nothing count is 0.
			}
			ret.count = count;
			if (count == 0)
			{
				ret.cis = new ClusterInfo[0];
				return ret;
			}

			Query query = em.createNamedQuery("getallusercluster");
			query.setParameter("userid", user.getIdAsString());
			query.setFirstResult(start);
			query.setMaxResults(limit);

			List lists = query.getResultList();
			List<ClusterInfo> acs = new LinkedList<ClusterInfo>();

			for (Object obj : lists)
			{
				Cluster ac = (Cluster) obj;
				ClusterInfo info = new ClusterInfo();
				info.clusterid = ac.getId();
				info.hostNum = ac.getHosts().size();
				info.note = ac.getNote();
				info.requestTime = ac.getRequestTime();
				info.status = ac.getStatus();
				info.elapsedHour = 0;
				Host head = ac.getHead();
				if (head != null)
				{
					int elapsed;
					if (head.getShutTime() != null)
						elapsed = (int) (ac.getHead().getShutTime().getTime() - ac.getHead().getStarttime().getTime()) / 3600000;
					else
						elapsed = (int) (new Date().getTime() - ac.getHead().getStarttime().getTime()) / 3600000;
					info.elapsedHour = elapsed + 1;
				}
				acs.add(info);
			}
			ret.cis = acs.toArray(new ClusterInfo[0]);
			tx.commit();
			return ret;
		} catch (Exception e)
		{
			logger.warn("user jobs error", e);
			throw new ServerException(e.getMessage());
		}
	}

}
